﻿using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.Globalization;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;

namespace InputParsing
{
    public class InputParser
    {
        private static NumberFormatInfo _numberFormat = new CultureInfo("en-US", false).NumberFormat; 
        #region Static Singleton Stuff

        private static volatile InputParser _instance;
        private static readonly object SyncRoot = new Object();
        public static InputParser Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (SyncRoot)
                    {
                        if (_instance == null)
                            _instance = new InputParser();
                    }
                }
                return _instance;
            }
        }

        #endregion

        #region Supporting stuff

        private readonly char[] _separators = new[] { ' ', '\t', '\n', '\r' };

        private unsafe BitmapResult FillBitmap(float[,] numbers, float min, float max, Color minColor, Color maxColor)
        {
            var gradeStepR = (maxColor.R - minColor.R)/(max - min);
            var gradeStepG = (maxColor.G - minColor.G)/(max - min);
            var gradeStepB = (maxColor.B - minColor.B)/(max - min);
            var bitmap = new Bitmap(numbers.GetLength(0), numbers.GetLength(1), PixelFormat.Format24bppRgb);
            var data = bitmap.LockBits(new Rectangle(new Point(0, 0), bitmap.Size), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            var pointer = (byte*)data.Scan0;
            var minR = minColor.R;
            var minG = minColor.G;
            var minB = minColor.B;
            for (int i = 0; i < bitmap.Height; i++)
            {
                for (int j = 0; j < bitmap.Width; j++)
                {
                    var temp = numbers[j, i];
                    if (temp < min)
                        temp = min;
                    else if (temp > max)
                        temp = max;
                    var buffer = (temp - min);
                    pointer[2] = (byte)(minR + buffer * gradeStepR);
                    pointer[1] = (byte)(minG + buffer * gradeStepG);
                    pointer[0] = (byte)(minB + buffer * gradeStepB);
                    pointer += 3;
                }
                pointer += data.Stride - data.Width * 3;
            }
            bitmap.UnlockBits(data);
            return new BitmapResult{Bitmap = bitmap, MaxValue = max, MinValue = min};
        }
        private float[,] GetFloatsFromFile(StreamReader reader)
        {
            var strings = reader.ReadLine().Split(_separators, StringSplitOptions.RemoveEmptyEntries);
            var height = (int)Single.Parse(strings[0], _numberFormat);
            var width = (int)Single.Parse(strings[1], _numberFormat);
            var numbers = new float[width, height];
            strings = reader.ReadToEnd().Split(_separators, StringSplitOptions.RemoveEmptyEntries);
            for (int i = 0; i < width; ++i)
                for (int j = 0; j < height; ++j)
                {
                    float temp = Single.Parse(strings[i * height + j], _numberFormat);
                    if(temp<0)
                        temp = -temp;
                    numbers[i, j] = temp;
                }
            return numbers;
        }
        #endregion
        #region public methods

        public BitmapResult ParseToBitmap(StreamReader reader, Color minColor, Color maxColor, float minValue = float.MinValue, float maxValue = float.MaxValue)
        {
            var arr = GetFloatsFromFile(reader);
            return ParseToBitmap(arr, minColor, maxColor, minValue, maxValue);
        }

        public BitmapResult ParseToBitmap(StreamReader reader, float minValue = float.MinValue, float maxValue = float.MaxValue)
        {
            var arr = GetFloatsFromFile(reader);
            return ParseToBitmap(arr, Color.Black, Color.White, minValue, maxValue);
        }

        public BitmapResult ParseToBitmap(float[,] numbers, float minValue=float.MinValue, float maxValue=float.MaxValue)
        {
            return ParseToBitmap(numbers, Color.Black, Color.White, minValue, maxValue);
        }

        public BitmapResult ParseToBitmap(float[,] numbers, Color minColor, Color maxColor, float minValue=float.MinValue, float maxValue=float.MaxValue)
        {
            var min = float.MaxValue;
            var max = float.MinValue;
            for (int i = 0; i < numbers.GetLength(0); ++i)
                for (int j = 0; j < numbers.GetLength(1); ++j)
                {
                    var temp = numbers[i, j];
                    if (temp < minValue)
                        temp = minValue;
                    else if (temp > maxValue)
                        temp = maxValue;
                    if (temp < min)
                        min = temp;
                    else if (temp > max)
                        max = temp;
                }
            return FillBitmap(numbers, min, max, minColor, maxColor);
        }
        public float[,] ParseToMatrix(StreamReader reader)
        {
            return GetFloatsFromFile(reader);
        }
        
        public IEnumerable<float[,]> ParseOneBigFile(string fileName)
        {
            string stringBuffer = null;
            using(var reader = new StreamReader(fileName))
                stringBuffer = reader.ReadToEnd();
            int lastIndex = 0;
            var count = (int)ReadToken(stringBuffer, ref lastIndex);
            var height = (int)ReadToken(stringBuffer, ref lastIndex);
            var width = (int)ReadToken(stringBuffer, ref lastIndex);
            for(int k = count;--k>=0;)
            {
                var numbers = new float[width, height];
                for (int i = 0; i < width; ++i)
                    for (int j = 0; j < height; ++j)
                    {
                        numbers[i, j] = ReadToken(stringBuffer, ref lastIndex);
                    }
                yield return numbers; 
            }
        }
        private float ReadToken(string buffer, ref int position)
        {
            while (Char.IsWhiteSpace(buffer[position]))
                position++;
            int pointer = position + 1;
            while (!Char.IsWhiteSpace(buffer[pointer]))
                pointer++;
            var substring = buffer.Substring(position, pointer - position);
            float temp = Single.Parse(substring, _numberFormat);
            if (temp < 0)
                temp = -temp;
            
            position = pointer;
            return temp;
        }
        #endregion
    }
}
